open Carte ;;

let valeur_of_carte = fst

let couleur_of_carte = snd

let supermax (carte1 : carte option) (carte2 : carte) : carte option =
  let choix =
    match carte1 with
    | None -> Some carte2
    | Some carte1
	 when carte2 = excuse && (demipoints carte1 >= 2) ->
       (* Si on a le choix entre quelque chose d'intéressant et l'excuse, on sauve la carte interessante *)
       Some carte1
    | Some carte1
	 when carte1 = excuse && (demipoints carte2 >= 2) ->
       Some carte2
    | Some carte1
	 when carte2 = excuse && (est_atout carte1) ->
       Some excuse (* On préfère jouer l'excuse plutôt que de sous-couper avec un atout *)
    | Some carte1
	 when carte1 = excuse && (est_atout carte2) ->
       Some excuse
    | Some carte1
	 when carte2 = excuse ->
       Some carte1 (* Une carte basse non atout plutôt que l'excuse *)
    | Some carte1
	 when carte1 = excuse ->
       Some carte2
    | Some (carte1 : carte) 
	 when demipoints carte1 > demipoints carte2
	      || (demipoints carte1 = demipoints carte2 
		  && valeur_of_carte carte1 < valeur_of_carte carte2)-> 
       Some carte1
    | Some _ -> Some carte2
  in
  (* let () = *)
  (*   match (carte1, choix) with *)
  (*   | (None, Some choix) -> () *)
  (*   | (Some carte1, Some choix) -> *)
  (*      Printf.printf "(Sauver %s ou %s ? %s) %!" *)
  (*      		     (Carte.string_of_carte carte1) *)
  (*      		     (Carte.string_of_carte carte2) *)
  (*      		     (Carte.string_of_carte choix) *)
  (*   | _ -> failwith "Ne peut arriver." *)
  (* in *)
  choix

let supermin (carte1 : carte option) (carte2 : carte) : carte option =
  let choix = 
    match carte1 with
    | None -> Some carte2
    | Some carte1 when carte2 = excuse && (est_atout carte1 || demipoints carte1 >= 2)
      -> Some carte2
    | Some (carte1 : carte) 
	 when demipoints carte1 < demipoints carte2
	      || (carte1 = excuse && (est_atout carte2 || demipoints carte2 >= 2))
	      (* On se débarasse en priorité de l'excuse, si l'autre possibilité est un atout *)
	      || (demipoints carte1 = demipoints carte2 
		  && valeur_of_carte carte1 < valeur_of_carte carte2) -> 
       Some carte1
    | Some _ -> Some carte2
  in
  (* let () = *)
  (*   match (carte1, choix) with *)
  (*   | (None, Some choix) -> () *)
  (*   | (Some carte1, Some choix) -> *)
  (*      Printf.printf "(Perdre %s ou %s ? %s) %!" *)
  (*      		     (Carte.string_of_carte carte1) *)
  (*      		     (Carte.string_of_carte carte2) *)
  (*      		     (Carte.string_of_carte choix) *)
  (*   | _ -> failwith "Ne peut arriver." *)
  (* in *)
  choix

let forcer = function
  | Some x -> x
  | None -> failwith "Le forçage a échoué."

let rec jouer_tour ?(verbeux=false)
		   repartition
		   preneur
		   appele
		   demandee_
		   debut_pli
		   dernier_pli =
  let () =
    if verbeux then
      let () = Printf.printf "Tour suivant :\n%!" in
      for i = 0 to 4 do
	Printf.printf
	  "%d a les cartes suivantes : %s. %!"
	  i
	  (let liste = List.map (Carte.string_of_carte)
				repartition.(i) in
	   String.concat ", " liste);
	match debut_pli.(i) with
	| None -> Printf.printf "Il n'a pas encore joué.\n%!"
	| Some carte ->
	   Printf.printf "Il a joué %s.\n%!"
			 (Carte.string_of_carte carte)
      done
  in
  (* Si besoin, on tire la couleur demandée au sort *)
  let demandee =
    match demandee_ with
    | None ->
       couleur_of_int (Random.int 5)
    | Some (Carte.Excuse) ->
       failwith "Couleur demandée : excuse"
    | Some c -> c
  in
  let () =
    if verbeux then
      let () =
	Printf.printf "Couleur demandée : %s. %!"
		      (Carte.string_of_couleur demandee)
      in
      if demandee_ = None then
	Printf.printf "Je l'ai tirée au sort.\n%!"
      else Printf.printf "C'était imposé.\n%!"
  in
  (* On restreint le jeu de chacun à la carte qu'ils sont obligés de jouer *)
  let repartition_reelle =
    Array.mapi
      (fun joueur main ->
       match debut_pli.(joueur) with
       | None -> main
       | Some obligee -> [obligee])
      (Array.sub repartition 0 5)
  in
  let () =
    if verbeux then
      for i = 0 to 4 do
	Printf.printf
	  "%d a réellement le choix parmi : %s\n%!"
	  i
	  (let liste = List.map (Carte.string_of_carte)
				repartition_reelle.(i) in
	   String.concat ", " liste)
      done
  in
  (* On détermine les cartes de la couleur demandée de chacun *)
  let cartes_fournissant = Array.make 5 [] in
  let () =
    Array.iteri
      (fun joueur main ->
       List.iter (function
		   | (valeur, couleur)
			when couleur = demandee ->
		      cartes_fournissant.(joueur) <-
			valeur :: cartes_fournissant.(joueur)
		   | _ -> ())
		 main)
      repartition_reelle
  in
  (* On détermine la carte la plus forte de la couleur demandée de chacun *)
  let carte_fournissant_max =
    Array.map
      (function
	| [] -> None
	| (a :: b) -> Some (List.fold_left (max) a b))
      cartes_fournissant
  in
  (* On détermine les atouts de chacun *)
  let atouts = Array.make 5 [] in
  let () =
    Array.iteri
      (fun joueur main ->
       List.iter (function
		   | (valeur, Carte.Atout) ->
		      atouts.(joueur) <- valeur :: atouts.(joueur)
		   | _ -> ())
		 main)
      repartition_reelle
  in
  (* On détermine l'atout max de chacun *)
  let atout_max =
    Array.map
      (function
	| [] -> None
	| (a :: b) -> Some (List.fold_left (max) a b))
      atouts
  in
  (* On détermine qui doit fournir *)
  let doit_fournir =
    Array.map
      ((<>) None)
      carte_fournissant_max
  in
  let () =
    if verbeux then
      for i = 0 to 4 do
	let () =
	  match atout_max.(i) with
	  | None -> Printf.printf "%d ne peut pas jouer d'atout, %!"
				  i
	  | Some valeur -> Printf.printf "L'atout max de %d est %s, %!"
					 i (Carte.string_of_carte (valeur, Carte.Atout))
	in
	let () =
	  match carte_fournissant_max.(i) with
	  | None -> Printf.printf "et il ne peut pas fournir.\n%!"
	  | Some valeur ->
	     Printf.printf "et il peut fournir en mettant au mieux la carte %s.\n%!"
			   (Carte.string_of_carte (valeur, demandee))
	in
	()
      done
  in
  let (joueur_maitre, carte_maitresse) =
    (* 1er cas : quelqu'un va couper (ie il ne va pas fournir -sauf si la couleur demandée est atout- et il a un atout). *)
    (* On liste tous les gens qui doivent mettre un atout, et on garde celui qui a le plus gros. *)
    let liste_coupants =
      let pile = ref [] in
      for i=0 to -1 + Array.length repartition_reelle do
	if ((not doit_fournir.(i)) || demandee = Carte.Atout)
	   && (atout_max.(i) <> None)
	then
	  let () =
	    if verbeux then
	      Printf.printf "%d peut mettre le %d d'atout au mieux.\n%!"
			    i (forcer atout_max.(i))
	  in
	  pile := (i, (forcer atout_max.(i), Carte.Atout)) :: (!pile)
	else ()
      done ;
      !pile
    in
    let max (i1, (a1, _)) (i2, (a2, _)) =
      if a1 > a2
      then (i1, (a1, Carte.Atout))
      else (i2, (a2, Carte.Atout))
    in
    match liste_coupants with
    | a :: b ->
       List.fold_left (max) a b
    | [] -> (* Échec, personne ne va couper *)
       (* 2ème cas : parmi tous les gens qui doivent fournir, c'est celui qui a la plus forte carte qui gagne *)
       let liste_fournissant =
	 let pile = ref [] in
	 for i=0 to -1 + Array.length repartition_reelle do
	   match carte_fournissant_max.(i) with
	   | Some cm ->
	      let () =
		if verbeux then
		  Printf.printf "%d peut mettre la carte %s au mieux.\n%!"
				i (Carte.string_of_carte (cm, demandee))
	      in
	      pile := (i, (cm, demandee)) :: (!pile)
	   | _ -> ()
	 done ;
	 !pile
       in
       let max (i1, (a1, _)) (i2, (a2, _)) =
	 if a1 > a2
	 then (i1, (a1, demandee))
	 else (i2, (a2, demandee))
       in
       match liste_fournissant with
       | a :: b -> List.fold_left (max) a b
       | [] -> (* Échec, personne ne fournit. On doit recommencer. *)
	  (-1, Carte.petit)
  in
  (* A-t-on trouvé le joueur maître ? *)
  if joueur_maitre < 0 then
    let () =
      if verbeux then
	Printf.printf
	  "On ne peut pas jouer la couleur %s. On recommence.\n%!"
	  (Carte.string_of_couleur demandee)
    in
    jouer_tour repartition preneur appele demandee_ debut_pli dernier_pli
	       (* À voir : peut-être qu'il faut remplacer demandee_ par None *)
  else (* On a bien trouvé le joueur maître *)
    let () =
      if verbeux then
	Printf.printf
	  "Le joueur maître est %d, sa carte maîtresse est %s.\n%!"
	  joueur_maitre (Carte.string_of_carte carte_maitresse)
    in
    (* On détermine les cartes jouables *)
    let jouables =
      Array.mapi
	(fun joueur main ->
	 if joueur = joueur_maitre
	 then
	   let () =
	     if verbeux then
	       Printf.printf "%d est le joueur maître, il doit jouer %s.\n%!"
			     joueur (Carte.string_of_carte carte_maitresse)
	   in
	   [carte_maitresse]
	 else if List.length main = 1
	 then
	   let () =
	     if verbeux then
	       Printf.printf "%d ne peut jouer que %s.\n%!"
			     joueur (Carte.string_of_carte (List.hd main))
	   in
	   main
	 else if doit_fournir.(joueur)
	 then
	   let jouables =
	     List.filter
	       (fun (v, c) -> c = demandee || c = Carte.Excuse)
	       main
	   in
	   let () =
	     if verbeux then
	       Printf.printf "%d doit fournir, il peut jouer une carte parmi %s.\n%!"
			     joueur (String.concat
				       ", "
				       (List.map
					  (Carte.string_of_carte)
					  jouables))
	   in
	   jouables
	 else if atout_max.(joueur) <> None 
	   (* il doit sous-couper (on a joué la carte maîtresse) *)
	 then
	   let jouables =
	     List.filter
	       (fun (v, c) -> c = Carte.Atout || c = Carte.Excuse)
	       main
	   in
	   let () =
	     if verbeux then
	       Printf.printf "%d doit couper, il peut jouer une carte parmi %s.\n%!"
			     joueur (String.concat
				       ", "
				       (List.map
					  (Carte.string_of_carte)
					  jouables))
	   in
	   jouables
	 else
	   let () =
	     if verbeux then
	       Printf.printf "%d peut jouer ce qu'il veut parmi %s.\n%!"
			     joueur (String.concat
				       ", "
				       (List.map
					  (Carte.string_of_carte)
					  main))
	   in
	   main)
	repartition_reelle
    in
    (* On détermine les équipes *)
    let attaque = Array.make 5 false in
    let () = attaque.(preneur) <- true in
    let () = attaque.(appele) <- true in
    (* On détermine chaque carte jouée en fonction de si l'on va gagner ou pas *)
    let resultat =
      Array.mapi
	(fun i jouables ->
	 let meilleur =
	   if attaque.(i) = attaque.(joueur_maitre)
	   then
	     let () =
	       if verbeux then
		 Printf.printf "%d est dans le camp du joueur maître. %!"
			       i
	     in
	     supermax
	   else 
	     let () =
	       if verbeux then
		 Printf.printf "%d est contre le joueur maître. %!"
			       i
	     in
	     supermin
	 in
	 match List.fold_left (meilleur) None jouables with
	 | None -> failwith "Pas de carte jouable"
	 | Some jouee ->
	    let () =
	      if verbeux then
		Printf.printf "Il joue %s.\n%!"
			      (Carte.string_of_carte jouee)
	    in
	    jouee)
	jouables
    in
    (* On compte les points *)
    let scores = Array.make 5 0 in
    let bouts = Array.make 5 0 in
    (* On attribue les demi-points (donc : 8 pour l'excuse) au joueur maître *)
    let () = Array.iter (fun carte ->
			 scores.(joueur_maitre) <- (Carte.demipoints carte)
						   + scores.(joueur_maitre))
			resultat in
    (* On attribue les bouts au joueur maître *)
    let () = Array.iter
	       (fun carte ->
		if Carte.est_bout carte
		then bouts.(joueur_maitre) <- 1 + bouts.(joueur_maitre))
	       resultat in
    (* On parcourt le résultat à la recherche de l'excuse. *)
    let () = Array.iteri
	       (fun joueur carte ->
		if carte = Carte.excuse
		then (* Si on en est au dernier tour, le joueur maître rajoute un demi-point (la carte basse) *)
		  if dernier_pli
		  then scores.(joueur_maitre) <-
			 1 + scores.(joueur_maitre)
		  else (* Le joueur maître n'a pas marqué les 8 dp, seulement 1 *)
		    let () = scores.(joueur_maitre) <-
			       scores.(joueur_maitre) - 7 in
		    (* Le joueur "joueur" marque par contre 8 dp *)
		    let () = scores.(joueur) <- scores.(joueur) + 8 in
		    (* Le joueur maître n'a pas réellement remporté un bout *)
		    let () = bouts.(joueur_maitre) <-
			       bouts.(joueur_maitre) - 1 in
		    (* Le joueur "joueur" a par contre remporté un bout *)
		    bouts.(joueur) <- bouts.(joueur) + 1
		else ())
	       resultat in
    let () =
      if verbeux then
	let () =
	  Array.iteri
	    (fun joueur dp ->
	     Printf.printf "Si on ne tient pas compte de son équipe, %d marque %d%s points.\n%!"
			   joueur (dp / 2) (if dp mod 2 = 0 then "" else ",5"))
	    scores
	in
	Array.iteri
	  (fun joueur bouts ->
	   Printf.printf "Si on ne tient pas compte de son équipe, %d remporte %d bouts.\n%!"
			 joueur bouts)
	  bouts
    in
    (* On met en commun les points/bouts de l'équipe *)
    let dp_attaque = ref 0 in
    let bouts_attaque = ref 0 in
    let dp_defense = ref 0 in
    let bouts_defense = ref 0 in
    let () = Array.iteri
	       (fun joueur dp ->
		if attaque.(joueur) then
		  dp_attaque := !dp_attaque + dp
		else
		  dp_defense := !dp_defense + dp)
	       scores in
    let () = Array.iteri
	       (fun joueur bouts ->
		if attaque.(joueur) then
		  bouts_attaque := !bouts_attaque + bouts
		else
		  bouts_defense := !bouts_defense + bouts)
	       bouts in
    let () =
      for i = 0 to 4 do
	let () =
	  scores.(i) <-
	    if attaque.(i) then !dp_attaque
	    else !dp_defense
	in
	bouts.(i) <-
	  if attaque.(i) then !bouts_attaque
	  else !bouts_defense
      done
    in
    let () =
      if verbeux then
	Printf.printf "L'attaque remporte %d%s points et %d bouts, la défense %d%s points et %d bouts.\n%!"
		      (!dp_attaque / 2) (if !dp_attaque mod 2 = 0 then "" else ",5")
		      !bouts_attaque
		      (!dp_defense / 2) (if !dp_defense mod 2 = 0 then "" else ",5")
		      !bouts_defense
    in
    let () =
      if verbeux then
	let () =
	  Array.iteri
	    (fun joueur dp ->
	     Printf.printf "%d marque %d%s points.\n%!"
			   joueur (dp / 2) (if dp mod 2 = 0 then "" else ",5"))
	    scores
	in
	Array.iteri
	  (fun joueur bouts ->
	   Printf.printf "%d remporte %d bouts.\n%!"
			 joueur bouts)
	  bouts
    in
    (* On biffe les cartes jouées *)
    let () =
      Array.iteri
	(fun joueur carte ->
	repartition.(joueur) <- List.filter ((<>) carte) repartition.(joueur))
	resultat
    in
    (scores, bouts)
